import React, { lazy, memo, Suspense, useEffect } from 'react';
import NProgress from 'nprogress';

interface LoadableParams {
  loading?: React.ReactNode;
  load: () => Promise<{
    default: React.ComponentType;
  }>;
  meta?: {
    title?: string;
  };
  Provider?: React.ComponentType<{ children: React.ReactNode }>;
  render?: <P = unknown>(Component: React.ComponentType, props: P) => React.ReactNode;
}

type LoadableFn = (params: LoadableParams) => React.FC;

const NProgressFC: React.FC = () => {
  useEffect(() => {
    NProgress.start();

    return () => {
      NProgress.done();
    };
  }, []);

  return null;
};

const Loadable: LoadableFn = ({ load, render }) => {
  const AsyncNode = lazy(load);
  return (props) => (
    <Suspense fallback={<NProgressFC />}>
      {render ? render(AsyncNode, props) : <AsyncNode {...props} />}
    </Suspense>
  );
};

export const LoadElement = ({
  load,
  render,
  Provider = React.Fragment,
  ...props
}: LoadableParams) => {
  const AsyncNode = lazy(load);

  const Component = memo((props) => {
    return (
      <Suspense fallback={<NProgressFC />}>
        <Provider>
          <AsyncNode {...props} />
        </Provider>
      </Suspense>
    );
  });

  if (render) {
    return (
      <>
        {render((props) => {
          return <Component {...props} />;
        }, props)}
      </>
    );
  } else {
    return <Component {...props} />;
  }
};

export default Loadable;
